package com.kuaishou.locallife.open.api.client;

import java.io.IOException;
import java.net.Proxy;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.kuaishou.locallife.open.api.KsLocalLifeApiException;
import com.kuaishou.locallife.open.api.KsLocalLifeClient;
import com.kuaishou.locallife.open.api.KsLocalLifeRequest;
import com.kuaishou.locallife.open.api.KsLocalLifeResponse;
import com.kuaishou.locallife.open.api.common.Constants.Client;
import com.kuaishou.locallife.open.api.common.Constants.ContentType;
import com.kuaishou.locallife.open.api.common.Constants.Headers;
import com.kuaishou.locallife.open.api.common.dto.KsHttpResponseDTO;
import com.kuaishou.locallife.open.api.common.http.HttpClient;
import com.kuaishou.locallife.open.api.common.http.HttpCommonRequest;
import com.kuaishou.locallife.open.api.common.http.HttpCommonResponse;
import com.kuaishou.locallife.open.api.common.http.KsHttpContextHolder;
import com.kuaishou.locallife.open.api.common.http.net.KsJdkHttpClient;
import com.kuaishou.locallife.open.api.common.utils.GsonUtils;
import com.kuaishou.locallife.open.api.common.utils.KsStringUtils;
import com.kuaishou.locallife.open.api.common.utils.LoggerUtils;
import com.kuaishou.locallife.open.api.common.utils.ObjectUtils;
import com.kuaishou.locallife.open.api.common.utils.ValidateUtils;
import com.kuaishou.locallife.open.api.common.utils.WebUtils;

/**
 * @author gaojiapei <gaojiapei@kuaishou.com>
 * Created on 2023-02-03
 */
public class KsLocalLifeAccessTokenClient implements KsLocalLifeClient {

    protected static final Logger logger = Logger.getLogger(KsLocalLifeAccessTokenClient.class.getName());

    // 接口调用凭证
    protected String accessToken;

    // 服务接口地址(host)
    protected String serverRestUrl;

    // api连接超时(ms)
    protected int connectTimeout;

    // api接口读超时(ms)
    protected int readTimeout;

    // api请求contentType
    protected String contentType;

    // api代理
    private Proxy proxy;

    private HttpClient httpClient;

    /**
     * http请求日志打印开关, 默认为开启状态
     */
    private boolean logSwitch;

    /**
     * 是否调用快手prt预发布测试环境
     */
    private boolean isPrtEnv;

    /**
     * 快手测试环境泳道Id
     */
    private String laneId;

    @Override
    public <T extends KsLocalLifeResponse> T execute(KsLocalLifeRequest<T> request) throws KsLocalLifeApiException {
        this.log(Level.INFO, KsLocalLifeAccessTokenClient.class.getSimpleName() + ": execute req: " + GsonUtils.toJSON(request));
        // 调用目标Api,解析resp
        KsHttpContextHolder contextHolder = this.invokeApi(request);
        // Json -> object
        T response = GsonUtils.fromJSON(contextHolder.getResponseBody(), request.getResponseClass());
        if (response == null) {
            this.log(Level.WARNING,KsLocalLifeAccessTokenClient.class.getSimpleName() + ": response json to Object error");
            throw new KsLocalLifeApiException("ks locallife api responseBody parse failed");
        }

        response.setRequests(contextHolder.getRequests());
        this.log(Level.INFO, KsLocalLifeAccessTokenClient.class.getSimpleName() + ": execute resp: " + GsonUtils.toJSON(response));

        return response;
    }

    @Override
    public HttpCommonResponse execute(HttpCommonRequest request) throws KsLocalLifeApiException {
        this.log(Level.INFO,KsLocalLifeAccessTokenClient.class.getSimpleName() + ": execute req: " + GsonUtils.toJSON(request));

        // 参数校验
        ValidateUtils.notBlank(request.getApiSpecialPath(), "Api path is required");
        ObjectUtils.requireNonNull(request.getHttpRequestMethod(), "Http request method is required");

        // 1.把access-token添加到header中,用于鉴权
        Map<String, String> headers = request.getHeaderParams();
        if (!headers.containsKey(Headers.ACCESS_TOKEN)) {
            headers.put(Headers.ACCESS_TOKEN, this.accessToken);
        }

        // 2.获取请求响应上下文
        KsHttpContextHolder contextHolder = this.invokeApi(request);

        // 3.响应转换
        HttpCommonResponse commonResponse = new HttpCommonResponse();
        commonResponse.setResponse(contextHolder.getResponseBody());
        commonResponse.setRequestUrl(contextHolder.getRequests().getHttp());

        this.log(Level.INFO, KsLocalLifeAccessTokenClient.class.getSimpleName() + ": execute resp: " + GsonUtils.toJSON(commonResponse));

        return commonResponse;
    }

    private <T extends KsLocalLifeResponse> KsHttpContextHolder invokeApi(KsLocalLifeRequest<T> request)
            throws KsLocalLifeApiException {
        HttpCommonRequest commonRequest = buildHttpCommonRequest(request);

        return this.invokeApi(commonRequest);
    }

    private HttpCommonRequest buildHttpCommonRequest(KsLocalLifeRequest request) {
        HttpCommonRequest commonRequest = new HttpCommonRequest();
        commonRequest.setApiParams(request.getApiParams());
        commonRequest.setApiMethodName(request.getApiMethodName());
        commonRequest.setApiReqJson(request.getApiReqJson());

        // access-token放入请求头
        Map<String, String> headers = request.getHeaderParams();
        headers.put(Headers.ACCESS_TOKEN, this.accessToken);
        commonRequest.setHeaderParams(headers);

        commonRequest.setApiSpecialPath(request.getRequestSpecialPath());
        commonRequest.setHttpRequestMethod(request.getHttpRequestMethod());

        return commonRequest;
    }

    protected KsHttpContextHolder invokeApi(HttpCommonRequest request) throws KsLocalLifeApiException {
        KsHttpContextHolder contextHolder = new KsHttpContextHolder();

        try {
            // 1.获取请求url
            String realServerUrl = getRealServerUrl(this.serverRestUrl, request.getApiSpecialPath());
            if (KsStringUtils.isBlank(realServerUrl)) {
                throw new KsLocalLifeApiException(String.format("server url: %s method: %s invalid",
                        this.serverRestUrl, request.getApiMethodName()));
            }
            contextHolder.setRequestUrl(realServerUrl);
            // 2.设置业务参数
            contextHolder.setBusinessParams(request.getApiParams());

            // 3.发起请求
            KsHttpResponseDTO httpResponseDTO;
            switch (request.getHttpRequestMethod()) {
                case GET:
                    httpResponseDTO = httpClient.doGet(realServerUrl, request.getHeaderParams(), contextHolder.getAllParams(), this.getProxy());
                    break;
                case POST:
                    httpResponseDTO = httpClient.doPost(realServerUrl, request.getHeaderParams(), request.getApiReqJson(), this.getProxy());
                    break;
                default:
                    throw new KsLocalLifeApiException(String.format("HttpRequestMethod: %s not support",
                            request.getHttpRequestMethod().toString()));
            }

            // 4.请求路径、响应信息填充
            KsLocalLifeResponse.Requests requests = new KsLocalLifeResponse.Requests();
            requests.setHttp(WebUtils.buildRequestUrl(realServerUrl,
                    WebUtils.buildQueryParams(contextHolder.getAllParams(), "UTF-8")));
            contextHolder.setRequests(requests);
            contextHolder.setResponseBody(httpResponseDTO.getBody());
            contextHolder.setResponseHeaders(httpResponseDTO.getHeaders());

            return contextHolder;
        } catch (IOException ex) {
            LoggerUtils.logApiError(logger, request.getApiMethodName(), request.getApiSpecialPath(),
                    request.getApiParams(), ex);
            throw new KsLocalLifeApiException(ex);
        }
    }

    private String getRealServerUrl(String serverUrl, String path) {
        if (serverUrl.endsWith("/")) {
            serverUrl = serverUrl.substring(0, serverUrl.length() - 1);
        }
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        return String.format("%s%s", serverUrl, path);
    }

    private void log(Level level, String content) {
        if (!this.logSwitch) {
            return;
        }
        if (level.equals(Level.INFO)) {
            LoggerUtils.info(logger, content);
        } else if (level.equals(Level.WARNING)) {
            LoggerUtils.warn(logger, content);
        }
    }

    public String getAccessToken() {
        return accessToken;
    }

    public String getServerRestUrl() {
        return serverRestUrl;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public String getContentType() {
        return contentType;
    }

    public Proxy getProxy() {
        return proxy;
    }

    public HttpClient getHttpClient() {
        return httpClient;
    }

    public boolean isLogSwitch() {
        return logSwitch;
    }

    public boolean isPrtEnv() {
        return isPrtEnv;
    }

    public static final class Builder {
        private String accessToken;
        private String serverRestUrl;
        private int connectTimeout;
        private int readTimeout;
        private String contentType;
        private Proxy proxy;
        private HttpClient httpClient;
        private boolean logSwitch;

        private boolean isPrtEnv;

        private String laneId;

        public Builder() {
            this.connectTimeout = 15000;
            this.readTimeout = 30000;
            this.logSwitch = true;
            this.contentType = ContentType.JSON;
            this.serverRestUrl = Client.DEFAULT_SERVER_REST_URL;
        }

        public static Builder newBuilder() {
            return new Builder();
        }

        public Builder setAccessToken(String accessToken) {
            this.accessToken = accessToken;
            return this;
        }

        public Builder setServerRestUrl(String serverRestUrl) {
            this.serverRestUrl = serverRestUrl;
            return this;
        }

        public Builder setConnectTimeout(int connectTimeout) {
            this.connectTimeout = connectTimeout;
            return this;
        }

        public Builder setReadTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
            return this;
        }

        public Builder setContentType(String contentType) {
            this.contentType = contentType;
            return this;
        }

        public Builder setProxy(Proxy proxy) {
            this.proxy = proxy;
            return this;
        }

        public Builder setHttpClient(HttpClient httpClient) {
            this.httpClient = httpClient;
            return this;
        }

        public Builder setLogSwitch(boolean logSwitch) {
            this.logSwitch = logSwitch;
            return this;
        }

        public Builder setIsPrtEnv(boolean isPrtEnv) {
            this.isPrtEnv = isPrtEnv;
            return this;
        }

        public Builder setLaneId(String laneId) {
            this.laneId = laneId;
            return this;
        }

        public KsLocalLifeAccessTokenClient build() {
            KsLocalLifeAccessTokenClient localLifeAccessTokenClient = new KsLocalLifeAccessTokenClient();
            // base field
            localLifeAccessTokenClient.accessToken = this.accessToken;
            localLifeAccessTokenClient.serverRestUrl = this.serverRestUrl;
            localLifeAccessTokenClient.connectTimeout = this.connectTimeout;
            localLifeAccessTokenClient.readTimeout = this.readTimeout;
            localLifeAccessTokenClient.contentType = this.contentType;
            localLifeAccessTokenClient.proxy = this.proxy;
            localLifeAccessTokenClient.logSwitch = this.logSwitch;
            localLifeAccessTokenClient.isPrtEnv = this.isPrtEnv;
            localLifeAccessTokenClient.laneId = this.laneId;

            if (this.httpClient == null) {
                localLifeAccessTokenClient.httpClient = KsJdkHttpClient.Builder.newBuilder()
                        .setConnectTimeout(localLifeAccessTokenClient.connectTimeout)
                        .setReadTimeout(localLifeAccessTokenClient.readTimeout)
                        .setContentType(localLifeAccessTokenClient.contentType)
                        .setIsPrtEnv(localLifeAccessTokenClient.isPrtEnv)
                        .setLaneId(localLifeAccessTokenClient.laneId)
                        .build();
            } else {
                localLifeAccessTokenClient.httpClient = this.httpClient;
            }


            return localLifeAccessTokenClient;
        }
    }
}
